﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Web;
using Microsoft.SharePoint;
using Microsoft.Office.Server;
using Microsoft.Office.Server.Administration;
using Microsoft.Office.Server.UserProfiles;


namespace LOS.SP.CleanOrphanedUsers
{
    class Program
    {
        private static string strSite = string.Empty;
        private static bool blnRecursive = false;
        private static bool blnCleanAlerts = false;
        private static bool blnTestMode = false;
        private static bool blnInputError = false;

        static void Main(string[] args)
        {
            if (args.Length > 0)
            {
                GetArgs(args);
            }
            else
            {
                WriteHelp();
                blnInputError = true;
            }


            if (!blnInputError)
            {
                if (strSite != string.Empty)
                {
                    BeginCleaning();
                }
            }
        }

        private static void WriteHelp()
        {
            Console.WriteLine("A utility used to clean up orphaned users from site collections");
            Console.WriteLine("-site            The URL for the site collection to check");
            Console.WriteLine("[-recursive]     Perform operations on subwebs also.");
            Console.WriteLine("[-cleanalerts]   Delete user alerts that belong to any orphaned users also.");
            Console.WriteLine("[-testmode]      Do nothing but report back which change will be made.");
            Console.WriteLine("[-?]             Help.");
        }

        private static void GetArgs(string[] args)
        {
            //## Use a for statement instead of a foreach, because we want to have an easy way to pull back the array item we are currently on while looping through the array.
            for (int i = 0; i < args.Length; i++)
            {
                //## Find out which "switch" we are on in the args array.
                switch (args[i])
                {
                    case "-site":
                        i++; //## We want the value (next arg) of this switch, so increase "i" by 1 before we grab the array value.                            
                        strSite = args[i];
                        break;
                    case "-recursive":
                        blnRecursive = true;
                        break;
                    case "-cleanalerts":
                        blnCleanAlerts = true;
                        break;
                    case "-testmode":
                        blnTestMode = true;
                        break;
                    case "-?":
                        WriteHelp();
                        break;
                    case "/?":
                        WriteHelp();
                        break;
                    default:
                        Console.WriteLine("There was an error in the input arguments. Please check that you have input all values correctly.");
                        blnInputError = true;
                        break;
                }
            }
        }

        private static void BeginCleaning()
        {
            try
            {
                using (SPSite site = new SPSite(strSite))
                {
                    using (SPWeb web = site.OpenWeb())
                    {
                        ServerContext context = ServerContext.GetContext(site);
                        UserProfileManager profileManager = new UserProfileManager(context);

                        ProcessWeb(web, profileManager);

                        if (blnRecursive)
                        {
                            if (web.Webs.Count > 0)
                            {
                                ProcessChildObjects(web, profileManager);
                            }
                        }
                    }
                }
            }
            catch (UnauthorizedAccessException)
            {
                Console.WriteLine("You do not have sufficient permissions to access this object. Try using an account with higher permissions.");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine();
                Console.WriteLine(ex.StackTrace);
            }
        }
        

        private static void ProcessWeb(SPWeb web, UserProfileManager profileManager)
        {
            List<SPUser> OrphanedUsers = GetOrphanedUsers(web, profileManager);

            if (!blnTestMode)
            {
                CleanOrphanedUsersFromObject(web, OrphanedUsers);
            }
            else
            {
                WriteTestModeData(web, OrphanedUsers);
            }

            //## Clean the SharePoint groups that are in this web
            ProcessGroups(web, profileManager);

            //## Removing this because the user is automatically added to the site level, even if the list has unique permissions.
            //## I will keep it around though, in case Microsoft decides to change that for the next version.
            //## Clean any lists that are in this web
            //ProcessLists(web, profileManager);
        }

        private static void ProcessLists(SPWeb web, UserProfileManager profileManager)
        {
            using (web)
            {
                foreach (SPList list in web.Lists)
                {
                    if (list.HasUniqueRoleAssignments)
                    {
                        List<SPUser> OrphanedUsers = GetOrphanedUsers(list, profileManager);

                        if (!blnTestMode)
                        {
                            CleanOrphanedUsersFromObject(list, OrphanedUsers);
                        }
                        else
                        {
                            WriteTestModeData(list, OrphanedUsers);
                        }
                    }

                    if (list.Folders.Count > 0)
                    {
                        ProcessFolders(list, profileManager);
                    }
                }
            }
        }

        private static void ProcessFolders(SPList list, UserProfileManager profileManager)
        {
            if (list.BaseType == SPBaseType.DocumentLibrary)
            {
                SPDocumentLibrary docLib = (SPDocumentLibrary)list;

                //## We don't want the site catalogs
                if (!docLib.IsCatalog && list.BaseTemplate != SPListTemplateType.XMLForm)
                {
                    foreach (SPListItem folder in docLib.Folders)
                    {
                        if (folder.Folder.Item.HasUniqueRoleAssignments)
                        {
                            List<SPUser> OrphanedUsers = GetOrphanedUsers(folder.Folder, profileManager);

                            if (!blnTestMode)
                            {
                                CleanOrphanedUsersFromObject(folder.Folder, OrphanedUsers);
                            }
                            else
                            {
                                WriteTestModeData(folder.Folder, OrphanedUsers);
                            }
                        }

                        if (folder.Folder.SubFolders.Count > 0)
                        {
                            ProcessChildObjects(folder.Folder, profileManager);
                        }
                    }
                }
            }
        }

        private static void ProcessGroups(SPWeb web, UserProfileManager profileManager)
        {
            using (web)
            {
                foreach (SPGroup group in web.Groups)
                {
                    List<SPUser> OrphanedUsers = GetOrphanedUsers(group, profileManager);

                    if (!blnTestMode)
                    {
                        CleanOrphanedUsersFromObject(group, OrphanedUsers);
                    }
                    else
                    {
                        WriteTestModeData(group, OrphanedUsers);
                    }
                }
            }
        }
        

        private static List<SPUser> GetOrphanedUsers(SPWeb web, UserProfileManager profileManager)
        {
            List<SPUser> OrphandUsers = new List<SPUser>();

            foreach (SPUser user in web.Users)
            {
                AddUserToOrphanedUsersList(profileManager, OrphandUsers, user);
            }

            return OrphandUsers;

        }

        private static List<SPUser> GetOrphanedUsers(SPList list, UserProfileManager profileManager)
        {
            List<SPUser> OrphandUsers = new List<SPUser>();

            foreach (SPRoleAssignment role in list.RoleAssignments)
            {
                if (role.Member is SPUser)
                {
                    {
                        SPUser user = (SPUser)role.Member;

                        AddUserToOrphanedUsersList(profileManager, OrphandUsers, user);
                    }
                }
            }

            return OrphandUsers;
        }

        private static List<SPUser> GetOrphanedUsers(SPFolder folder, UserProfileManager profileManager)
        {
            List<SPUser> OrphandUsers = new List<SPUser>();


            foreach (SPRoleAssignment role in folder.Item.RoleAssignments)
            {
                if (role.Member is SPUser)
                {
                    SPUser user = (SPUser)role.Member;

                    AddUserToOrphanedUsersList(profileManager, OrphandUsers, user);
                }
            }

            return OrphandUsers;
        }

        private static List<SPUser> GetOrphanedUsers(SPGroup group, UserProfileManager profileManager)
        {
            List<SPUser> OrphandUsers = new List<SPUser>();


            foreach (SPUser user in group.Users)
            {
                AddUserToOrphanedUsersList(profileManager, OrphandUsers, user);
            }

            return OrphandUsers;
        }

        private static bool IsValidUser(SPUser user)
        {
            if (user.LoginName.ToLower().StartsWith("system") || user.LoginName.ToLower().StartsWith("nt authority"))
                return false;

            return true;
        }

        private static void AddUserToOrphanedUsersList(UserProfileManager profileManager, List<SPUser> OrphandUsers, SPUser user)
        {
            if (!user.IsDomainGroup)
            {
                if (IsValidUser(user))
                {
                    if (!profileManager.UserExists(user.ToString()))
                    {
                        OrphandUsers.Add(user);
                    }
                }
            }
        }


        private static void CleanOrphanedUsersFromObject(SPWeb web, List<SPUser> OrphanedUsers)
        {

            Console.WriteLine("---------------------------------");
            Console.WriteLine("Site: " + web.Name.ToString());

            foreach (SPUser user in OrphanedUsers)
            {

                web.Users.Remove(user.LoginName);
                Console.WriteLine("Removed user: " + user.LoginName);

                if (blnCleanAlerts)
                {
                    //## If the user has alerts, then clean the alerts.
                    if (user.Alerts.Count > 0)
                        CleanAlerts(web, user);

                    //## Check for any subwebs that inherit permissions.
                    if (web.Webs.Count > 0)
                        ProcessChildObjects(web, user);
                }

            }

        }

        private static void CleanOrphanedUsersFromObject(SPList list, List<SPUser> OrphanedUsers)
        {
            Console.WriteLine("---------------------------------");
            Console.WriteLine("List: " + list.Title.ToString());

            foreach (SPUser spuser in OrphanedUsers)
            {
                SPUser user = list.ParentWeb.Users[spuser.LoginName];

                list.RoleAssignments.Remove((SPPrincipal)user);

                Console.WriteLine("Removed user: " + user.LoginName);
            }
        }

        private static void CleanOrphanedUsersFromObject(SPFolder folder, List<SPUser> OrphanedUsers)
        {
            Console.WriteLine("---------------------------------");
            Console.WriteLine("Folder: " + folder.Name.ToString());

            foreach (SPUser spuser in OrphanedUsers)
            {
                SPUser user = folder.ParentWeb.Users[spuser.LoginName];

                folder.Item.RoleAssignments.Remove((SPPrincipal)user);

                Console.WriteLine("Removed user: " + user.LoginName);
            }
        }

        private static void CleanOrphanedUsersFromObject(SPGroup group, List<SPUser> OrphanedUsers)
        {
            Console.WriteLine("---------------------------------");
            Console.WriteLine("Group: " + group.Name.ToString());

            foreach (SPUser user in OrphanedUsers)
            {
                group.Users.Remove(user.LoginName);

                Console.WriteLine("Removed user: " + user.LoginName);
            }
        }
        

        private static void CleanAlerts(SPWeb web, SPUser user)
        {
            if (user.Alerts.Count > 0)
            {
                foreach (SPAlert alert in user.Alerts)
                {
                    try
                    {
                        web.Alerts.Delete(alert.ID);
                        Console.WriteLine("      -Deleted alert: {0}", alert.Title);
                    }
                    catch
                    {
                        //## Put in an empty try/catch here because SharePoint has a bad habit of creating multiple Alert GUIDs for one alert. The problem is
                        //## when you delete one, it deletes them all. Without this, the job would try to delete an alert that was just deleted.
                    }
                }
            }
        }
        

        private static void ProcessChildObjects(SPWeb parentWeb, UserProfileManager profileManager)
        {
            foreach (SPWeb web in parentWeb.Webs)
            {
                List<SPUser> OrphanedUsers = GetOrphanedUsers(web, profileManager);

                if (!blnTestMode)
                {
                    CleanOrphanedUsersFromObject(web, OrphanedUsers);
                }
                else
                {
                    WriteTestModeData(web, OrphanedUsers);
                }

                //## Removing this because the user is automatically added to the site level, even if the list has unique permissions.
                //## I will keep it around though, in case Microsoft decides to change that for the next version.
                //ProcessLists(web, profileManager);

                if (web.Webs.Count > 0)
                {
                    ProcessChildObjects(web, profileManager);
                }

                web.Dispose();
            }

        }

        private static void ProcessChildObjects(SPFolder parentFolder, UserProfileManager profileManager)
        {
            foreach (SPFolder folder in parentFolder.SubFolders)
            {
                List<SPUser> OrphanedUsers = GetOrphanedUsers(folder, profileManager);

                if (!blnTestMode)
                {
                    CleanOrphanedUsersFromObject(folder, OrphanedUsers);
                }
                else
                {
                    WriteTestModeData(folder, OrphanedUsers);
                }

                if (folder.SubFolders.Count > 0)
                {
                    ProcessChildObjects(folder, profileManager);
                }

            }
        }

        private static void ProcessChildObjects(SPWeb parentWeb, SPUser user)
        {

            foreach (SPWeb web in parentWeb.Webs)
            {
                if (!web.HasUniqueRoleAssignments)
                {
                    CleanAlerts(web, user);

                    if (web.Webs.Count > 0)
                        ProcessChildObjects(web, user);
                }

                web.Dispose();
            }

        }
        

        private static void WriteTestModeData(SPWeb web, List<SPUser> OrphandedUsers)
        {

            Console.WriteLine("------------------------------------");
            Console.WriteLine("Site Name: " + web.Name.ToString());

            if (OrphandedUsers.Count > 0)
            {

                foreach (SPUser user in OrphandedUsers)
                {
                    Console.WriteLine("User to delete: " + user.LoginName);

                    if (blnCleanAlerts)
                    {
                        if (user.Alerts.Count > 0)
                        {
                            foreach (SPAlert alert in web.Alerts)
                            {
                                Console.WriteLine("    -delete alert: {0}", alert.Title);
                            }
                        }

                        if (web.Webs.Count > 0)
                            WriteTestModeData(web, user);
                    }
                }


            }
            else
            {
                Console.WriteLine("No users to remove.");
            }

        }

        private static void WriteTestModeData(SPList list, List<SPUser> OrphandedUsers)
        {
            Console.WriteLine("------------------------------------");
            Console.WriteLine("List Name: " + list.Title.ToString());

            if (OrphandedUsers.Count > 0)
            {

                foreach (SPUser user in OrphandedUsers)
                {
                    Console.WriteLine("User to delete: " + user.LoginName);
                }
            }
            else
            {
                Console.WriteLine("No users to remove.");
            }
        }

        private static void WriteTestModeData(SPFolder folder, List<SPUser> OrphandedUsers)
        {
            Console.WriteLine("------------------------------------");
            Console.WriteLine("Folder Name: " + folder.Name.ToString());

            if (OrphandedUsers.Count > 0)
            {

                foreach (SPUser user in OrphandedUsers)
                {
                    Console.WriteLine("User to delete: " + user.LoginName);
                }
            }
            else
            {
                Console.WriteLine("No users to remove.");
            }
        }

        private static void WriteTestModeData(SPGroup group, List<SPUser> OrphandedUsers)
        {
            Console.WriteLine("------------------------------------");
            Console.WriteLine("Group Name: " + group.Name.ToString());

            if (OrphandedUsers.Count > 0)
            {

                foreach (SPUser user in OrphandedUsers)
                {
                    Console.WriteLine("User to delete: " + user.LoginName);
                }
            }
            else
            {
                Console.WriteLine("No users to remove.");
            }
        }

        private static void WriteTestModeData(SPWeb parentWeb, SPUser user)
        {

            foreach (SPWeb web in parentWeb.Webs)
            {
                if (!web.HasUniqueRoleAssignments)
                {
                    if (user.Alerts.Count > 0)
                    {
                        foreach (SPAlert alert in web.Alerts)
                        {
                            Console.WriteLine("  Subweb with inherited permissions: {0}", web.Title);
                            Console.WriteLine("    -delete alert: {0}", alert.Title);
                        }
                    }

                    if (web.Webs.Count > 0)
                        WriteTestModeData(web, user);
                }

                web.Dispose();
            }

        }

    }
}
